package com.moli.iov.common.mongodb.autoconfigure;

import com.moli.iov.common.mongodb.properties.MongoDbProperties;
import com.mongodb.*;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

import static com.moli.iov.common.mongodb.constants.MongoConstant.CONCAT_SEPARATOR;
import static com.moli.iov.common.mongodb.constants.MongoConstant.URI_SEPARATOR;
import static com.moli.iov.common.mongodb.constants.MongoConstant.SEPARATOR;

/**
 *
 * {@link EnableAutoConfiguration Auto-configuration} for iov Mongo clients.
 *
 * @author OAK
 * @version  3.0.1
 * @since 2019/12/13 10:12:00 PM.
 *
 */
@Configuration
@ConditionalOnClass(MongoClient.class)
@EnableConfigurationProperties(MongoDbProperties.class)
public class AutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MongoClient client(MongoDbProperties properties) throws MongoClientException {

        List<String> uris = properties.getUris();

        if(uris == null){
            throw new MongoClientException("Mongo uris not found.");
        }

        MongoClient mongoClient = null;

        Integer size = uris.size(), counter = 0;

        List<ServerAddress> seeds = new ArrayList<>(size);

        StringBuilder mongoUris = new StringBuilder();

        for(String uri : uris){
            if(uri.contains(URI_SEPARATOR)){
                mongoUris.append(uri);
                if (counter != (size - 1)) {
                    mongoUris.append(CONCAT_SEPARATOR);
                }
                counter++;
            } else if(uri.contains(SEPARATOR)) {
                String [] hostAndPort = uri.split(":");
                seeds.add(new ServerAddress(hostAndPort[0], new Integer(hostAndPort[1])));
            }
        }

        if(seeds.size() > 0){
            MongoClientOptions.Builder builder = MongoClientOptions.builder();
            PropertyMapper map = PropertyMapper.get();
            map.from(properties::getDescription).whenHasText().to(builder::description);
            map.from(properties::getApplicationName).whenHasText().to(builder::applicationName);
            map.from(properties::getMinConnectionsPerHost).whenNonNull().to(builder::minConnectionsPerHost);
            map.from(properties::getConnectionsPerHost).whenNonNull().to(builder::connectionsPerHost);
            map.from(properties::getThreadsAllowedToBlockForConnectionMultiplier)
                    .whenNonNull().to(builder::threadsAllowedToBlockForConnectionMultiplier);
            map.from(properties::getServerSelectionTimeout).whenNonNull().to(builder::serverSelectionTimeout);
            map.from(properties::getMaxWaitTime).whenNonNull().to(builder::maxWaitTime);
            map.from(properties::getMaxConnectionIdleTime).whenNonNull().to(builder::maxConnectionIdleTime);
            map.from(properties::getMaxConnectionLifeTime).whenNonNull().to(builder::maxConnectionLifeTime);
            map.from(properties::getConnectTimeout).whenNonNull().to(builder::connectTimeout);
            map.from(properties::getSocketTimeout).whenNonNull().to(builder::socketTimeout);
            map.from(properties::getSocketKeepAlive).whenNonNull().to(builder::socketKeepAlive);
            map.from(properties::getSslEnabled).whenNonNull().to(builder::sslEnabled);
            map.from(properties::getSslInvalidHostNameAllowed).whenNonNull().to(builder::sslInvalidHostNameAllowed);
            map.from(properties::getCursorFinalizerEnabled).whenNonNull().to(builder::cursorFinalizerEnabled);
            map.from(properties::getAlwaysUseMBeans).whenNonNull().to(builder::alwaysUseMBeans);
            map.from(properties::getHeartbeatFrequency).whenNonNull().to(builder::heartbeatFrequency);
            map.from(properties::getMinHeartbeatFrequency).whenNonNull().to(builder::minHeartbeatFrequency);
            map.from(properties::getHeartbeatConnectTimeout).whenNonNull().to(builder::heartbeatConnectTimeout);
            map.from(properties::getHeartbeatSocketTimeout).whenNonNull().to(builder::heartbeatSocketTimeout);
            map.from(properties::getLocalThreshold).whenNonNull().to(builder::localThreshold);
            MongoClientOptions mongoClientOptions = builder.build();
            mongoClient = new MongoClient(seeds, mongoClientOptions);
        } else if(mongoUris.length() > 0){
            mongoClient = new MongoClient(new MongoClientURI(mongoUris.toString()));
        }else{
            throw new MongoClientException("Mongo client information not found.");
        }
        return mongoClient;
    }

    @Bean
    @ConditionalOnMissingBean
    public MongoDbFactory mongoDbFactory(MongoDbProperties properties, MongoClient client) throws MongoClientException {
        String database = properties.getDatabase();
        if(!StringUtils.hasText(database)){
            throw new MongoClientException("Mongo client database not found.");
        }
        return new SimpleMongoDbFactory(client, database);
    }

    @Bean
    @ConditionalOnMissingBean
    public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) throws MongoClientException {
        return new MongoTemplate(mongoDbFactory);
    }
}
